#pragma once
#include <string>
#include <vector>
#include <cassert>
#include "chip8_def.hpp"

namespace chip8{

static const uint8 chip8_fontset[80] =
{ 
  0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
  0x20, 0x60, 0x20, 0x20, 0x70, // 1
  0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
  0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
  0x90, 0x90, 0xF0, 0x10, 0x10, // 4
  0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
  0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
  0xF0, 0x10, 0x20, 0x40, 0x40, // 7
  0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
  0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
  0xF0, 0x90, 0xF0, 0x90, 0x90, // A
  0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
  0xF0, 0x80, 0x80, 0x80, 0xF0, // C
  0xE0, 0x90, 0x90, 0x90, 0xE0, // D
  0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
  0xF0, 0x80, 0xF0, 0x80, 0x80  // F
};

class Chip8 final {
public:

    Chip8()
    {

    }

    void initialize() {
        pc     = 0x200;  // Program counter starts at 0x200
        opcode = 0;      // Reset current opcode	
        I      = 0;      // Reset index register
        sp     = 0;      // Reset stack pointer
        
        // Clear display	
        memset(gfx, 0, sizeof(gfx));
        drawFlag = true;
        // Clear stack
        memset(stack, 0, sizeof(stack));
        sp = 0;
        // Clear registers V0-VF
        memset(V, 0, sizeof(V));
        // Clear memory
        memset(memory, 0, sizeof(memory));
        memset(key, 0, sizeof(key));
        
        // Load fontset
        for(int i = 0; i < 80; ++i)
            memory[i] = chip8_fontset[i];		
        
        // Reset timers
        sound_timer = 0;
        delay_timer = 0;
    }

    void loadGame(const char* filepath) {
        // TODO: 
    }

    template<typename T>
    void loadGame(T& qtLikeInputStream) {
        int p = 0x200;
        while (!qtLikeInputStream.atEnd() && p < sizeof(memory)) {
            qtLikeInputStream >> memory[p++];
        }
    }

    Chip8Error emulateCycle() {
        assert(0 <= pc && pc + 1 < sizeof(memory));
        opcode = memory[pc] << 8 | memory[pc + 1];
        auto ret = operate();
        return ret;
    }

    void setDrawFlag(uint8 drawFlag) {
        this->drawFlag = drawFlag;
    }

    const uint8& getDrawFlag() const {
        return drawFlag; 
    }

    const uint8& getSoundTimer() const {
        return sound_timer;
    }

    uint8* getGfx() {
        return gfx;
    }

    uint16 getPc() {
        return pc;
    }

    uint16 getOpcode() {
        return opcode;
    }

    void keyPress(int key) {
        assert(key >= 0 && key < 16);
        this->key[key] = 1;
    }

    void keyRelease(int key) {
        assert(key >= 0 && key < 16);
        this->key[key] = 0;
    }

    void timerStep() {
        // TODO: beep not implemented.
        delay_timer = delay_timer > 0 ? delay_timer - 1 : 0;
        sound_timer = sound_timer > 0 ? sound_timer - 1 : 0;
    }

protected:
    uint8 V[16];  // 十六个寄存器，分别是V0、V1、……，V14、VE
    uint8& VF = V[15];  // carry flag: 进位标志

    uint16 I;  // Index register：索引寄存器
    uint16 pc;  // program counter：程序计数器

    uint16 stack[16];  // 栈
    uint16 sp;  // 指向当前的空位。sp=0则栈空，sp=17则栈满。

    // 当前的操作码
    uint16 opcode;

    // 内存
    uint8 memory[4096];  // 共4k内存

    // 定时器
    uint8 delay_timer;
    uint8 sound_timer;

    // 输入
    uint8 key[16];  // 16个输入按键

    // 显示器
    uint8 drawFlag = 1;  // 是否应该更新显示器
    unsigned char gfx[64 * 32];  // 显示器，32行，64列。

private:

    Chip8Error operate() {
        // for (int i = 0; i < 16; ++i) {
        //     printf("v%d: %d; ", i, V[i]);
        // }
        // printf("\n");
        bool wait = false;
        // 参考：https://en.wikipedia.org/wiki/CHIP-8#Opcode_table
        switch(opcode & 0xF000) {
            case 0x0000:
                if (opcode == 0x00E0) {  // 清空显示器
                    memset(gfx, 0, sizeof(gfx));
                    drawFlag = true;
                    pc += 2;
                }
                else if(opcode == 0x00EE) {  // return
                    if (sp <= 0 || sp >= 17)
                        return Chip8Error::StackError;
                    pc = stack[--sp];
                    pc += 2;
                }
                else 
                    return Chip8Error::UnrecognizedOpcode;
            break;
            case 0x1000:  // 1NNN: jump
                pc = opcode & 0x0FFF;
                if (pc >= 4096)
                    return Chip8Error::IndexOutOfRange;
            break;
            case 0x2000:  // 2NNN: call subroutine at NNN
                if (sp < 0 || sp >= 16)
                    return Chip8Error::StackError;
                stack[sp++] = pc;
                pc = opcode & 0x0FFF;
            break;
            case 0x3000:  // 3XNN: condition
                if (V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF))
                    pc += 4;
                else 
                    pc += 2;
            break; 
            case 0x4000:  // 4XNN: condition
                if (V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF))
                    pc += 4;
                else
                    pc += 2;
            break;
            case 0x5000:
                switch (opcode & 0x000F)
                {
                case 0x0000:  // 5XY0: condition
                    if (V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4])
                        pc += 4;
                    else
                        pc += 2;
                break;
                default:
                    return Chip8Error::UnrecognizedOpcode;
                }
            break;
            case 0x6000:  // 6XNN: set VX to NN
                V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
                pc += 2;
            break; 
            case 0x7000:  //7XNN: VX += NN
                V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
                pc += 2;
            break;
            case 0x8000:
                switch (opcode & 0x000F) {
                    case 0x0000:  // 0x8XY0: VX = VY
                        V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;
                    case 0x0001:  // 0x8XY1: VX = VX | VY
                        V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;
                    case 0x0002:  // 0x8XY2: VX = VX & VY
                        V[(opcode & 0x0F00) >> 8] &= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;
                    case 0x0003:  // 0x8XY3: VX = VX ^ VY
                        V[(opcode & 0x0F00) >> 8] ^= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;
                    case 0x0004:  // 0x8XY4: adds the value of VY to VX
                    {
                        auto x = (opcode & 0x0F00) >> 8;
                        auto y = (opcode & 0x00F0) >> 4;
                        uint8& vx = V[x];
                        const uint8& vy = V[y];
                        VF = vx > 0xFF - vy ? 1 : 0;  // 进位标志
                        vx += vy;
                        pc += 2;
                    }
                    break;
                    case 0x0005:  // 8XY5: VX -= VY
                    {   
                        // subt
                        auto x = (opcode & 0x0F00) >> 8;
                        auto y = (opcode & 0x00F0) >> 4;
                        uint8& vx = V[x];
                        const uint8 vy = V[y];
                        VF = vx < vy ? 0 : 1;
                        vx -= vy;
                        pc += 2;
                    }
                    break;
                    case 0x0006:  // 8XY6: VX >>= 1
                    {
                        auto x = (opcode & 0x0F00) >> 8;
                        uint8& vx = V[x];
                        VF = vx & 0x0001;
                        vx >>= 1;
                        pc += 2;
                    }
                    break;
                    case 0x0007:  // 8XY7: VX = VY - VX
                    {
                        uint8& vx = V[(opcode & 0x0F00) >> 8];
                        const uint8& vy = V[(opcode & 0x00F0) >> 4];
                        VF = vy >= vx;
                        vx = vy - vx;
                        pc += 2;
                    }
                    break;
                    case 0x000E:  // 8XYE: VX <<= 1
                    {
                        uint8& vx = V[(opcode & 0x0F00) >> 8];
                        VF = (vx >> 7);
                        vx <<= 1;
                        pc += 2;
                    }
                    break;
                    default:
                        return Chip8Error::UnrecognizedOpcode;
                }
            break;
            case 0x9000:  // 9XY0: condition
                if (V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4])
                    pc += 4;
                else
                    pc += 2;
            break;
            case 0xA000:  // ANNN: I = NNN
                I = opcode & 0x0FFF;
                pc += 2;
            break;
            case 0xB000:  // BNNN: PC = V0 + NNN
                pc = V[0] + (opcode & 0x0FFF);
            break;
            case 0xC000:  // CXNN: VX = rand() & NN
                V[(opcode & 0x0F00) >> 8] = (rand() % 256) & (opcode & 0x00FF);
                pc += 2;
            break;
            case 0xD000:  // DXYN: draw 
            {
                unsigned short x = V[(opcode & 0x0F00) >> 8] % 64;
                unsigned short y = V[(opcode & 0x00F0) >> 4] % 32;
                unsigned short height = opcode & 0x000F;
                unsigned short pixel;
                if (I + height > 4096)
                    return Chip8Error::IndexOutOfRange;
                
                VF = 0;
                for (int yline = 0; yline < height; yline++)
                {
                    pixel = memory[I + yline];
                    for(int xline = 0; xline < 8; xline++)
                    {
                        if((pixel & (0x80 >> xline)) != 0)
                        {   
                            int ax = x + xline;
                            int ay = y + yline;
                            if (ax < 0 || ax >= 64 || ay < 0 || ay >= 32) {
                                continue;
                            }
                            auto index = ((ay * 64) + ax) % (64 * 32);
                            if(gfx[index] == 1)
                                VF = 1;                                 
                            gfx[index] ^= 1;
                        }
                    }
                }
                drawFlag = true;
                pc += 2;
            }
            break;
            case 0xE000:
                switch (opcode & 0x00FF) {
                    case 0x009E:  // EX9E: if key stored in VX is pressed then skip
                    {
                        const uint8 vx = V[(opcode & 0x0F00) >> 8];
                        if (vx < 0 || vx >= 16)
                            return Chip8Error::IndexOutOfRange;
                        if (key[vx])
                            pc += 4;
                        else 
                            pc += 2;
                    }
                    break;
                    case 0x00A1:  // EXA1: if key stored in VX is not pressed then skip
                    {
                        const uint8 vx = V[(opcode & 0x0F00) >> 8];
                        if (vx < 0 || vx >= 16)
                            return Chip8Error::IndexOutOfRange;
                        if (!key[vx])
                            pc += 4;
                        else 
                            pc += 2;
                    }
                    break;
                    default:
                    return Chip8Error::UnrecognizedOpcode;
                }
            break;
            case 0xF000:
                switch (opcode & 0x00FF)
                {
                    case 0x0007: // FX07: Vx = get_delay()
                        V[(opcode & 0x0F00) >> 8] = delay_timer;
                        pc += 2;
                    break;
                    case 0x000A: // FX0A: Vx = get_key()
                        wait = true;
                        for (int i = 0; i < 16; ++i) {
                            if (key[i]) {
                                V[(opcode & 0x0F00) >> 8] = i;
                                pc += 2;
                                wait = false;
                            }
                        }
                    break;
                    case 0x0015: // FX15: Sets the delay timer to VX.
                        delay_timer = V[(opcode & 0x0F00) >> 8];
                        pc += 2;
                    break;
                    case 0x0018: // FX18: Sets the sound timer to VX.
                        sound_timer = V[(opcode & 0x0F00) >> 8];
                        pc += 2;
                    break;
                    case 0x001E: // FX1E: adds Vx to I
                        I += V[(opcode & 0x0F00) >> 8];
                        pc += 2;
                    break;
                    case 0x0029: // FX29: I = sprite_addr[Vx]
                    {
                        const uint8 vx = V[(opcode & 0x0F00) >> 8];
                        if (vx < 0 || vx >= 16)
                            return Chip8Error::IndexOutOfRange;
                        I = vx * 5;
                        pc += 2;
                    }
                    break;
                    case 0x0033: // FX33
                        memory[I]     = V[(opcode & 0x0F00) >> 8] / 100;
                        memory[I + 1] = (V[(opcode & 0x0F00) >> 8] / 10) % 10;
                        memory[I + 2] = V[(opcode & 0x0F00) >> 8] % 10;
                        pc += 2;
                    break;
                    case 0x0055: // FX55: reg_dump(Vx,&I)
                    {
                        const uint8 x = (opcode & 0x0F00) >> 8;
                        if (I + x >= 4096)
                            return Chip8Error::IndexOutOfRange;
                        for (int i = 0; i <= x; ++i) {
                            memory[I + i] = V[i];
                        }
                        // 在某些chip-8中I增加到I+x+1，但在另一些版本中I不变。
                        I += x + 1;
                        pc += 2;
                    }
                    break;
                    case 0x0065: // FX65: reg_load(Vx,&I)
                    {
                        const uint8 x = (opcode & 0x0F00) >> 8;
                        if (I + x >= 4096)
                            return Chip8Error::IndexOutOfRange;
                        for (int i = 0; i <= x; ++i) {
                            V[i] = memory[I + i];
                        }
                        // 在某些chip-8中I增加到I+x+1，但在另一些版本中I不变。
                        I += x + 1;
                        pc += 2;
                    }
                    break;
                    default:
                    return Chip8Error::UnrecognizedOpcode;
                }
            break;
            default:
                return Chip8Error::UnrecognizedOpcode;            
        }
        return Chip8Error::OK;
    }

    /*
    * 前人实现的指令集，基本没有错误。
    */
    Chip8Error operate_truth() {
        // Process opcode
        switch(opcode & 0xF000)
        {		
            case 0x0000:
                switch(opcode & 0x000F)
                {
                    case 0x0000: // 0x00E0: Clears the screen
                        for(int i = 0; i < 2048; ++i)
                            gfx[i] = 0x0;
                        drawFlag = true;
                        pc += 2;
                    break;

                    case 0x000E: // 0x00EE: Returns from subroutine
                        --sp;			// 16 levels of stack, decrease stack pointer to prevent overwrite
                        pc = stack[sp];	// Put the stored return address from the stack back into the program counter					
                        pc += 2;		// Don't forget to increase the program counter!
                    break;

                    default:
                        printf ("Unknown opcode [0x0000]: 0x%X\n", opcode);					
                }
            break;

            case 0x1000: // 0x1NNN: Jumps to address NNN
                pc = opcode & 0x0FFF;
            break;

            case 0x2000: // 0x2NNN: Calls subroutine at NNN.
                stack[sp] = pc;			// Store current address in stack
                ++sp;					// Increment stack pointer
                pc = opcode & 0x0FFF;	// Set the program counter to the address at NNN
            break;
            
            case 0x3000: // 0x3XNN: Skips the next instruction if VX equals NN
                if(V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF))
                    pc += 4;
                else
                    pc += 2;
            break;
            
            case 0x4000: // 0x4XNN: Skips the next instruction if VX doesn't equal NN
                if(V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF))
                    pc += 4;
                else
                    pc += 2;
            break;
            
            case 0x5000: // 0x5XY0: Skips the next instruction if VX equals VY.
                if(V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4])
                    pc += 4;
                else
                    pc += 2;
            break;
            
            case 0x6000: // 0x6XNN: Sets VX to NN.
                V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
                pc += 2;
            break;
            
            case 0x7000: // 0x7XNN: Adds NN to VX.
                V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
                pc += 2;
            break;
            
            case 0x8000:
                switch(opcode & 0x000F)
                {
                    case 0x0000: // 0x8XY0: Sets VX to the value of VY
                        V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;

                    case 0x0001: // 0x8XY1: Sets VX to "VX OR VY"
                        V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;

                    case 0x0002: // 0x8XY2: Sets VX to "VX AND VY"
                        V[(opcode & 0x0F00) >> 8] &= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;

                    case 0x0003: // 0x8XY3: Sets VX to "VX XOR VY"
                        V[(opcode & 0x0F00) >> 8] ^= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;

                    case 0x0004: // 0x8XY4: Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't					
                        if(V[(opcode & 0x00F0) >> 4] > (0xFF - V[(opcode & 0x0F00) >> 8])) 
                            V[0xF] = 1; //carry
                        else 
                            V[0xF] = 0;					
                        V[(opcode & 0x0F00) >> 8] += V[(opcode & 0x00F0) >> 4];
                        pc += 2;					
                    break;

                    case 0x0005: // 0x8XY5: VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't
                        if(V[(opcode & 0x00F0) >> 4] > V[(opcode & 0x0F00) >> 8]) 
                            V[0xF] = 0; // there is a borrow
                        else 
                            V[0xF] = 1;					
                        V[(opcode & 0x0F00) >> 8] -= V[(opcode & 0x00F0) >> 4];
                        pc += 2;
                    break;

                    case 0x0006: // 0x8XY6: Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift
                        V[0xF] = V[(opcode & 0x0F00) >> 8] & 0x1;
                        V[(opcode & 0x0F00) >> 8] >>= 1;
                        pc += 2;
                    break;

                    case 0x0007: // 0x8XY7: Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't
                        if(V[(opcode & 0x0F00) >> 8] > V[(opcode & 0x00F0) >> 4])	// VY-VX
                            V[0xF] = 0; // there is a borrow
                        else
                            V[0xF] = 1;
                        V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4] - V[(opcode & 0x0F00) >> 8];				
                        pc += 2;
                    break;

                    case 0x000E: // 0x8XYE: Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift
                        V[0xF] = V[(opcode & 0x0F00) >> 8] >> 7;
                        V[(opcode & 0x0F00) >> 8] <<= 1;
                        pc += 2;
                    break;

                    default:
                        printf ("Unknown opcode [0x8000]: 0x%X\n", opcode);
                }
            break;
            
            case 0x9000: // 0x9XY0: Skips the next instruction if VX doesn't equal VY
                if(V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4])
                    pc += 4;
                else
                    pc += 2;
            break;

            case 0xA000: // ANNN: Sets I to the address NNN
                I = opcode & 0x0FFF;
                pc += 2;
            break;
            
            case 0xB000: // BNNN: Jumps to the address NNN plus V0
                pc = (opcode & 0x0FFF) + V[0];
            break;
            
            case 0xC000: // CXNN: Sets VX to a random number and NN
                V[(opcode & 0x0F00) >> 8] = (rand() % 0xFF) & (opcode & 0x00FF);
                pc += 2;
            break;
        
            case 0xD000: // DXYN: Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. 
                        // Each row of 8 pixels is read as bit-coded starting from memory location I; 
                        // I value doesn't change after the execution of this instruction. 
                        // VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, 
                        // and to 0 if that doesn't happen
            {
                unsigned short x = V[(opcode & 0x0F00) >> 8];
                unsigned short y = V[(opcode & 0x00F0) >> 4];
                unsigned short height = opcode & 0x000F;
                unsigned short pixel;

                V[0xF] = 0;
                for (int yline = 0; yline < height; yline++)
                {
                    pixel = memory[I + yline];
                    for(int xline = 0; xline < 8; xline++)
                    {
                        if((pixel & (0x80 >> xline)) != 0)
                        {   
                            int ax = x + xline;
                            int ay = y + yline;
                            if (ax < 0 || ax >= 64 || ay < 0 || ay >= 32)
                                continue;
                            if(gfx[(x + xline + ((y + yline) * 64))] == 1)
                            {
                                V[0xF] = 1;                                    
                            }
                            gfx[x + xline + ((y + yline) * 64)] ^= 1;
                        }
                    }
                }
                            
                drawFlag = true;			
                pc += 2;
            }
            break;
                
            case 0xE000:
                switch(opcode & 0x00FF)
                {
                    case 0x009E: // EX9E: Skips the next instruction if the key stored in VX is pressed
                        if(key[V[(opcode & 0x0F00) >> 8]] != 0)
                            pc += 4;
                        else
                            pc += 2;
                    break;
                    
                    case 0x00A1: // EXA1: Skips the next instruction if the key stored in VX isn't pressed
                        if(key[V[(opcode & 0x0F00) >> 8]] == 0)
                            pc += 4;
                        else
                            pc += 2;
                    break;

                    default:
                        printf ("Unknown opcode [0xE000]: 0x%X\n", opcode);
                }
            break;
            
            case 0xF000:
                switch(opcode & 0x00FF)
                {
                    case 0x0007: // FX07: Sets VX to the value of the delay timer
                        V[(opcode & 0x0F00) >> 8] = delay_timer;
                        pc += 2;
                    break;
                                    
                    case 0x000A: // FX0A: A key press is awaited, and then stored in VX		
                    {
                        bool keyPress = false;

                        for(int i = 0; i < 16; ++i)
                        {
                            if(key[i] != 0)
                            {
                                V[(opcode & 0x0F00) >> 8] = i;
                                keyPress = true;
                            }
                        }

                        // If we didn't received a keypress, skip this cycle and try again.
                        if(!keyPress)						
                            return Chip8Error::OK;

                        pc += 2;					
                    }
                    break;
                    
                    case 0x0015: // FX15: Sets the delay timer to VX
                        delay_timer = V[(opcode & 0x0F00) >> 8];
                        pc += 2;
                    break;

                    case 0x0018: // FX18: Sets the sound timer to VX
                        sound_timer = V[(opcode & 0x0F00) >> 8];
                        pc += 2;
                    break;

                    case 0x001E: // FX1E: Adds VX to I
                        if(I + V[(opcode & 0x0F00) >> 8] > 0xFFF)	// VF is set to 1 when range overflow (I+VX>0xFFF), and 0 when there isn't.
                            V[0xF] = 1;
                        else
                            V[0xF] = 0;
                        I += V[(opcode & 0x0F00) >> 8];
                        pc += 2;
                    break;

                    case 0x0029: // FX29: Sets I to the location of the sprite for the character in VX. Characters 0-F (in hexadecimal) are represented by a 4x5 font
                        I = V[(opcode & 0x0F00) >> 8] * 0x5;
                        pc += 2;
                    break;

                    case 0x0033: // FX33: Stores the Binary-coded decimal representation of VX at the addresses I, I plus 1, and I plus 2
                        memory[I]     = V[(opcode & 0x0F00) >> 8] / 100;
                        memory[I + 1] = (V[(opcode & 0x0F00) >> 8] / 10) % 10;
                        memory[I + 2] = (V[(opcode & 0x0F00) >> 8] % 100) % 10;					
                        pc += 2;
                    break;

                    case 0x0055: // FX55: Stores V0 to VX in memory starting at address I					
                        for (int i = 0; i <= ((opcode & 0x0F00) >> 8); ++i)
                            memory[I + i] = V[i];	

                        // On the original interpreter, when the operation is done, I = I + X + 1.
                        I += ((opcode & 0x0F00) >> 8) + 1;
                        pc += 2;
                    break;

                    case 0x0065: // FX65: Fills V0 to VX with values from memory starting at address I					
                        for (int i = 0; i <= ((opcode & 0x0F00) >> 8); ++i)
                            V[i] = memory[I + i];			

                        // On the original interpreter, when the operation is done, I = I + X + 1.
                        I += ((opcode & 0x0F00) >> 8) + 1;
                        pc += 2;
                    break;

                    default:
                        printf ("Unknown opcode [0xF000]: 0x%X\n", opcode);
                }
            break;

            default:
                printf ("Unknown opcode: 0x%X\n", opcode);
        }	
        return Chip8Error::OK;
    }


};

};